package com.ylr.base.framework.service.impl;

import com.ylr.base.common.enums.ResultEnum;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.framework.service.SnowflakeService;
import com.ylr.base.common.utils.PropertiesUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2022-09-04 14:19:56
 * className: SnowflakeServiceImpl 雪花主键生成服务接口实现类
 * version: 1.0
 * description:
 */
@Service("snowflakeService")
public class SnowflakeServiceImpl implements SnowflakeService {

    private static final Logger log = LoggerFactory.getLogger(SnowflakeServiceImpl.class);

    /**
     * 内部可重入锁对象
     */
    private final ReentrantLock lock = new ReentrantLock();

    /**
     * 数据中心id（0-31之间的数，左闭右闭区间）
     */
    private long dataId;

    /**
     * 工作机器id（0-31之间的数，左闭右闭区间）
     */
    private long workId;

    /**
     * 时间部分所占长度
     */
    private static final int TIME_LEN = 41;
    /**
     * 数据中心id所占长度
     */
    private static final int DATA_LEN = 5;
    /**
     * 机器id所占长度
     */
    private static final int WORK_LEN = 5;
    /**
     * 毫秒内序列所占长度
     */
    private static final int SEQ_LEN = 12;
    /**
     * 定义起始时间 2022-01-01 00:00:00:000
     */
    private static final long START_TIME = 1672502400000L;
    /**
     * 上次生成ID的时间截
     */
    private static long LAST_TIME_STAMP = -1L;
    /**
     * 时间部分向左移动的位数（TIME_LEFT_BIT = 22）
     */
    private static final int TIME_LEFT_BIT = 64 - 1 - TIME_LEN;
    /**
     * 数据中心id左移位数（DATA_LEFT_BIT = 17）
     */
    private static final int DATA_LEFT_BIT = TIME_LEFT_BIT - DATA_LEN;
    /**
     * 机器id左移位数（WORK_LEFT_BIT = 12）
     */
    private static final int WORK_LEFT_BIT = DATA_LEFT_BIT - WORK_LEN;
    /**
     * 上一次的毫秒内序列值
     */
    private static long LAST_SEQ = 0L;
    /**
     * 一毫秒内可生成的雪花主键个数（SEQ_MAX_NUM = 4095）
     */
    private static final long SEQ_MAX_NUM = ~(-1 << SEQ_LEN);

    private final PropertiesUtils propertiesUtils;

    private final ApplicationContext applicationContext;

    public SnowflakeServiceImpl(PropertiesUtils propertiesUtils, ApplicationContext applicationContext) {
        this.propertiesUtils = propertiesUtils;
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        String value = propertiesUtils.getPropertiesValue("${snowflake.serial-number}");
        try {
            if (StringUtils.isEmpty(value)) {
                throw new YlrException("配置文件没有配置‘服务序号’");
            }
            double serialNumber = Double.parseDouble(value);
            if (serialNumber < 0 || serialNumber > 1023) {
                throw new YlrException("‘服务序号’值配置错误（serialNumber=" + serialNumber + "），请取0~1023区间内的值");
            }
            // 获取机器id
            this.dataId = (long) Math.floor(serialNumber / 32);
            this.workId = (long) (serialNumber % 32);

            log.debug("雪花主键 dataId={} workId={}", dataId, workId);
        } catch (Exception e) {
            log.error("初始化雪花主键服务bean失败。message={}", e.getMessage());
            // 停止服务
            int exitCode = SpringApplication.exit(applicationContext, () -> 0);
            System.exit(exitCode);
        }
    }

    @Override
    public Long getId() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        long id;
        try {
            // 生成一个主键值
            id = genId();
        } finally {
            lock.unlock();
        }
        return id;
    }

    @Override
    public List<Long> batchGetId(int num) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        List<Long> idList = new ArrayList<>(num);
        try {
            for (int i = 0; i < num; i++) {
                // 生成一个主键值
                idList.add(genId());
            }
        } finally {
            lock.unlock();
        }
        return idList;
    }

    @Override
    public String finId() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        long id;
        try {
            // 生成一个主键值
            id = genId();
        } finally {
            lock.unlock();
        }
        return String.valueOf(id);
    }

    @Override
    public List<String> batchFinId(Integer num) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        List<String> idList = new ArrayList<>(num);
        try {
            for (int i = 0; i < num; i++) {
                // 生成一个主键值
                idList.add(finId());
            }
        } finally {
            lock.unlock();
        }
        return idList;
    }

    /**
     * 生成一个雪花主键值
     * @return 雪花主键值
     */
    private long genId(){
        long now = System.currentTimeMillis();

        //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (now < LAST_TIME_STAMP) {
            log.error("发生时间回拨，雪花主键生成异常，请务必及时处理。now={} lastTimeStamp={}", now, LAST_TIME_STAMP);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }

        if (now == LAST_TIME_STAMP) {
            LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
            if (LAST_SEQ == 0){
                now = nextMillis(LAST_TIME_STAMP);
            }
        } else {
            LAST_SEQ = 0;
        }

        //上次生成ID的时间截
        LAST_TIME_STAMP = now;

        return ((now - START_TIME) << TIME_LEFT_BIT) | (dataId << DATA_LEFT_BIT) | (workId << WORK_LEFT_BIT) | LAST_SEQ;
    }

    /**
     * 获取下一不同毫秒的时间戳，不能与最后的时间戳一样
     */
    private long nextMillis(long lastMillis) {
        long now = System.currentTimeMillis();
        while (now <= lastMillis) {
            now = System.currentTimeMillis();
        }
        return now;
    }


}
